Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
95.92% covered (success)
95.92%
94 / 98
66.67% covered (warning)
66.67%
4 / 6
CRAP
0.00% covered (danger)
0.00%
0 / 1
Serializer
95.92% covered (success)
95.92%
94 / 98
66.67% covered (warning)
66.67%
4 / 6
28
0.00% covered (danger)
0.00%
0 / 1
 __construct
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 create
100.00% covered (success)
100.00%
22 / 22
100.00% covered (success)
100.00%
1 / 1
1
 normalize
92.86% covered (success)
92.86%
26 / 28
0.00% covered (danger)
0.00%
0 / 1
12.05
 denormalizeOnMethodCall
100.00% covered (success)
100.00%
5 / 5
100.00% covered (success)
100.00%
1 / 1
2
 denormalizeNewObject
93.33% covered (success)
93.33%
28 / 30
0.00% covered (danger)
0.00%
0 / 1
11.04
 denormalizeOnExistingObject
100.00% covered (success)
100.00%
12 / 12
100.00% covered (success)
100.00%
1 / 1
1
1<?php
2namespace Apie\Serializer;
3
4use Apie\Core\Context\ApieContext;
5use Apie\Core\Exceptions\InvalidTypeException;
6use Apie\Core\Lists\ItemHashmap;
7use Apie\Core\Lists\ItemList;
8use Apie\Core\Metadata\Concerns\UseContextKey;
9use Apie\Core\Metadata\MetadataFactory;
10use Apie\Core\Utils\ConverterUtils;
11use Apie\Core\ValueObjects\Utils;
12use Apie\Serializer\Context\ApieSerializerContext;
13use Apie\Serializer\Context\NormalizeChildGroup;
14use Apie\Serializer\Exceptions\ValidationException;
15use Apie\Serializer\Interfaces\DenormalizerInterface;
16use Apie\Serializer\Interfaces\NormalizerInterface;
17use Apie\Serializer\Lists\NormalizerList;
18use Apie\Serializer\Normalizers\AliasDenormalizer;
19use Apie\Serializer\Normalizers\BooleanNormalizer;
20use Apie\Serializer\Normalizers\DateTimeNormalizer;
21use Apie\Serializer\Normalizers\DateTimeZoneNormalizer;
22use Apie\Serializer\Normalizers\DoNotChangeFileNormalizer;
23use Apie\Serializer\Normalizers\EnumNormalizer;
24use Apie\Serializer\Normalizers\FloatNormalizer;
25use Apie\Serializer\Normalizers\IdentifierNormalizer;
26use Apie\Serializer\Normalizers\IntegerNormalizer;
27use Apie\Serializer\Normalizers\ItemListNormalizer;
28use Apie\Serializer\Normalizers\PaginatedResultNormalizer;
29use Apie\Serializer\Normalizers\PermissionListNormalizer;
30use Apie\Serializer\Normalizers\PolymorphicObjectNormalizer;
31use Apie\Serializer\Normalizers\ReflectionTypeNormalizer;
32use Apie\Serializer\Normalizers\ResourceNormalizer;
33use Apie\Serializer\Normalizers\StringableCompositeValueObjectNormalizer;
34use Apie\Serializer\Normalizers\StringNormalizer;
35use Apie\Serializer\Normalizers\UploadedFileNormalizer;
36use Apie\Serializer\Normalizers\ValueObjectNormalizer;
37use Exception;
38use Psr\Http\Message\UploadedFileInterface;
39use ReflectionClass;
40use ReflectionMethod;
41
42class Serializer
43{
44    use UseContextKey;
45
46    public function __construct(private NormalizerList $normalizers)
47    {
48    }
49
50    /**
51     * @param iterable<int, NormalizerInterface|DenormalizerInterface> $additionalNormalizers
52     */
53    public static function create(iterable $additionalNormalizers = []): self
54    {
55        return new self(new NormalizerList([
56            ...$additionalNormalizers,
57            new AliasDenormalizer(),
58            new PaginatedResultNormalizer(),
59            new DoNotChangeFileNormalizer(),
60            new PermissionListNormalizer(),
61            new UploadedFileNormalizer(),
62            new IdentifierNormalizer(),
63            new StringableCompositeValueObjectNormalizer(),
64            new PolymorphicObjectNormalizer(),
65            new DateTimeNormalizer(),
66            new DateTimeZoneNormalizer(),
67            new ResourceNormalizer(),
68            new EnumNormalizer(),
69            new ValueObjectNormalizer(),
70            new StringNormalizer(),
71            new IntegerNormalizer(),
72            new FloatNormalizer(),
73            new BooleanNormalizer(),
74            new ItemListNormalizer(),
75            new ReflectionTypeNormalizer(),
76        ]));
77    }
78
79    public function normalize(mixed $object, ApieContext $apieContext, bool $forceDefaultNormalization = false): string|int|float|bool|ItemList|ItemHashmap|null
80    {
81        $serializerContext = new ApieSerializerContext($this, $apieContext);
82        if (!$forceDefaultNormalization) {
83            foreach ($this->normalizers->iterateOverNormalizers() as $normalizer) {
84                if ($normalizer->supportsNormalization($object, $serializerContext)) {
85                    return $normalizer->normalize($object, $serializerContext);
86                }
87            }
88        }
89        if (is_array($object)) {
90            $count = 0;
91            $returnValue = [];
92            $isList = true;
93            foreach ($object as $key => $value) {
94                if ($key === $count) {
95                    $count++;
96                } else {
97                    $isList = false;
98                }
99                $returnValue[$key] = $serializerContext->normalizeChildElement($key, $value);
100            }
101            return $isList ? new ItemList($returnValue) : new ItemHashmap($returnValue);
102        }
103        if (!is_object($object)) {
104            if (in_array(get_debug_type($object), ['resource', 'resource (closed)'])) {
105                throw new InvalidTypeException($object, 'primitive');
106            }
107            return $object;
108        }
109        $metadata = MetadataFactory::getResultMetadata(new ReflectionClass($object), $apieContext);
110        $returnValue = [];
111
112        foreach ($metadata->getHashmap()->filterOnContext($apieContext, getters: true) as $fieldName => $metadata) {
113            if ($metadata->isField()) {
114                $returnValue[$fieldName] = $serializerContext->normalizeChildElement(
115                    $fieldName,
116                    $metadata->getValue($object, $apieContext)
117                );
118            }
119        }
120        return new ItemHashmap($returnValue);
121    }
122
123    public function denormalizeOnMethodCall(string|int|float|bool|ItemList|ItemHashmap|array|null|UploadedFileInterface $input, ?object $object, ReflectionMethod $method, ApieContext $apieContext): mixed
124    {
125        $serializerContext = new ApieSerializerContext($this, $apieContext);
126        try {
127            $arguments = $serializerContext->denormalizeFromMethod($input, $method);
128        } catch (Exception $error) {
129            throw ValidationException::createFromArray(['' => $error]);
130        }
131        return $method->invokeArgs($object, $arguments);
132    }
133
134    public function denormalizeNewObject(string|int|float|bool|ItemList|ItemHashmap|array|null|UploadedFileInterface $object, string $desiredType, ApieContext $apieContext): mixed
135    {
136        if (is_array($object)) {
137            $isList = false;
138            if ($desiredType === 'mixed') {
139                $isList = true;
140                $count = 0;
141                foreach (array_keys($object) as $key) {
142                    if ($key === $count) {
143                        $count++;
144                    } else {
145                        $isList = false;
146                        break;
147                    }
148                }
149            }
150            $object = $isList ? new ItemList($object) : new ItemHashmap($object);
151        }
152        if ($desiredType === 'mixed') {
153            return $object;
154        }
155        $serializerContext = new ApieSerializerContext($this, $apieContext);
156        foreach ($this->normalizers->iterateOverDenormalizers() as $denormalizer) {
157            if ($denormalizer->supportsDenormalization($object, $desiredType, $serializerContext)) {
158                return $denormalizer->denormalize($object, $desiredType, $serializerContext);
159            }
160        }
161        $refl = ConverterUtils::toReflectionClass($desiredType);
162        if (!$refl || !$refl->isInstantiable()) {
163            throw new InvalidTypeException($desiredType, 'a instantiable object');
164        }
165        $metadata = MetadataFactory::getCreationMetadata(
166            $refl,
167            $apieContext
168        );
169        $group = new NormalizeChildGroup(
170            $serializerContext,
171            $metadata
172        );
173        $normalizedData = $group->buildNormalizedData($refl, Utils::toArray($object));
174        return $normalizedData->createNewObject();
175    }
176
177    public function denormalizeOnExistingObject(ItemHashmap $object, object $existingObject, ApieContext $apieContext): mixed
178    {
179        $refl = new ReflectionClass($existingObject);
180        $serializerContext = new ApieSerializerContext($this, $apieContext);
181        $metadata = MetadataFactory::getModificationMetadata(
182            $refl,
183            $apieContext
184        );
185        $group = new NormalizeChildGroup(
186            $serializerContext,
187            $metadata
188        );
189        $normalizedData = $group->buildNormalizedData($refl, Utils::toArray($object));
190        return $normalizedData->modifyExistingObject($existingObject);
191    }
192}